<script setup>
import { onMounted, ref } from 'vue';
import {
    Scene, PerspectiveCamera, WebGLRenderer, MeshBasicMaterial, Mesh, DirectionalLight, Color, PlaneGeometry, DoubleSide, AxesHelper,
    TextureLoader, MeshStandardMaterial, Shape, ExtrudeGeometry, ShapeGeometry, RepeatWrapping, BufferAttribute, Vector2, Raycaster,
    Texture,
} from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js'
import { OutlinePass } from 'three/addons/postprocessing/OutlinePass.js'
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
import { GammaCorrectionShader } from 'three/addons/shaders/GammaCorrectionShader.js';
import { FXAAShader } from 'three/addons/shaders/FXAAShader.js';
import { createLinearGradientCanvas } from '../../../utils/createGradientCanvas.js'
import Map from '../../../assets/img/shanghai-map.png';
import Shanghai from '../../../assets/json/shanghai_02.geojson';

const viewRef = ref();

onMounted(() => {
    const scene = new Scene();
    scene.background = new Color('#003f8f');
    const camera = new PerspectiveCamera(10, window.innerWidth / window.innerHeight, 0.1, 1000);
    camera.position.z = 50;

    const renderer = new WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);
    viewRef.value?.appendChild(renderer.domElement);

    const axesHelper = new AxesHelper(100);
    scene.add(axesHelper);

    const texture_loader = new TextureLoader();

    // effect
    const composer = new EffectComposer(renderer);
    const renderPass = new RenderPass(scene, camera);
    composer.addPass(renderPass);

    // unreal pass
    const unrealBloomPass = new UnrealBloomPass(new Vector2(window.innerWidth, window.innerHeight));
    unrealBloomPass.strength = 0.2;
    composer.addPass(unrealBloomPass);

    // outline pass
    const outlinePass = new OutlinePass(new Vector2(window.innerWidth, window.innerHeight), scene, camera);
    outlinePass.visibleEdgeColor.set('#003f8f');
    outlinePass.edgeStrength = 5; // 边缘强度
    outlinePass.edgeThickness = 1; // 边缘厚度
    composer.addPass(outlinePass);

    // gamma pass
    const gammaCorrectionPass = new ShaderPass(GammaCorrectionShader);
    composer.addPass(gammaCorrectionPass);

    // FXAA pass
    const FXAAPass = new ShaderPass(FXAAShader);
    const pixelRadio = renderer.getPixelRatio();
    FXAAPass.uniforms.resolution.value.x = 1 / (window.innerWidth * pixelRadio);
    FXAAPass.uniforms.resolution.value.y = 1 / (window.innerHeight * pixelRadio);
    composer.addPass(FXAAPass);

    const taskList = [];
    const animate = () => {
        taskList.forEach(t => t());
        requestAnimationFrame(animate);
        composer.render();
    }
    animate();

    // controller
    const controls = new OrbitControls(camera, renderer.domElement);

    // light
    const directionalLight = new DirectionalLight(0xffffff, 1);
    directionalLight.position.x = 0;
    directionalLight.position.y = 1;
    directionalLight.position.z = 0;
    scene.add(directionalLight);

    // ground
    const ground = new PlaneGeometry(20, 20);
    ground.rotateX(-Math.PI / 2);
    const texture = texture_loader.load(Map);
    texture.wrapS = RepeatWrapping;
    texture.wrapT = RepeatWrapping;
    texture.repeat.set(1, 1);
    const material = new MeshStandardMaterial({ color: new Color('#003f8f'), map: texture, side: DoubleSide });
    const plane = new Mesh(ground, material);
    scene.add(plane);

    // top attributes
    const [[x1, y1], [x2, y2]] = Shanghai.propertity.bbox;
    const topCenter = [(x1 + x2) / 2, (y1 + y2) / 2];
    const realWidth = Math.max(Math.abs(x1 - x2), Math.abs(y1 - y2));
    const topScale = 20 / realWidth;
    const uvOrigin = [Math.min(x1, x2), Math.min(y1, y2)];

    // region mesh + side mesh
    const region_meterial = new MeshStandardMaterial({ map: texture, side: DoubleSide });
    const side_texture = new Texture(createLinearGradientCanvas(64, '#003f8f'));
    side_texture.needsUpdate = true;
    const side_meterial = new MeshBasicMaterial({ map: side_texture, side: DoubleSide, depthWrite: false, transparent: true });
    const getPosition = p => [(p[0] - topCenter[0]) * topScale, (p[1] - topCenter[1]) * topScale];
    const getUVPosition = p => [(p[0] - uvOrigin[0]) / realWidth, (p[1] - uvOrigin[1]) / realWidth];

    const region_meshs = [];
    Shanghai.features.map(f => {
        const positions = f.geometry.coordinates;
        // get coordinates
        const shapes = [];
        // get uv
        const uvs = [];
        positions.forEach(([a]) => {
            const shape = new Shape();
            shape.moveTo(...getPosition(a[0]));
            a.slice(1).forEach(p => shape.lineTo(...getPosition(p)));
            shapes.push(shape);
            a.forEach(i => uvs.push(...getUVPosition(i)));
        });
        // region geometry
        const region_geometry = new ShapeGeometry(shapes);
        region_geometry.rotateX(-Math.PI / 2);
        region_geometry.setAttribute('uv', new BufferAttribute(new Float32Array(uvs), 2));
        region_geometry.translate(0, 0.2, 0);
        const region_mesh = new Mesh(region_geometry, region_meterial);
        region_mesh.userData = { is_region: true };
        scene.add(region_mesh);
        region_meshs.push(region_mesh);
        // side geometry
        const side_geometry = new ExtrudeGeometry(shapes, {
            bevelEnabled: false,
            depth: 0.2,
            UVGenerator: {
                generateTopUV: (geometry, vertices, indexA, indexB, indexC) => {
                    const a_x = vertices[indexA * 3] <= 0 ? 0 : 1;
                    const a_y = vertices[indexA * 3 + 1] <= 0 ? 0 : 1;
                    const b_x = vertices[indexB * 3] <= 0 ? 0 : 1;
                    const b_y = vertices[indexB * 3 + 1] <= 0 ? 0 : 1;
                    const c_x = vertices[indexC * 3] <= 0 ? 0 : 1;
                    const c_y = vertices[indexC * 3 + 1] <= 0 ? 0 : 1;
                    return [new Vector2(a_x, a_y), new Vector2(b_x, b_y), new Vector2(c_x, c_y)];
                },
                generateSideWallUV: (geometry, vertices, indexA, indexB, indexC, indexD) => {
                    const a_x = vertices[indexA * 3] <= 0 ? 0 : 1;
                    const a_y = vertices[indexA * 3 + 1] <= 0 ? 0 : 1;
                    const a_z = vertices[indexA * 3 + 2] <= 0 ? 0 : 1;
                    const b_x = vertices[indexB * 3] <= 0 ? 0 : 1;
                    const b_y = vertices[indexB * 3 + 1] <= 0 ? 0 : 1;
                    const b_z = vertices[indexB * 3 + 2] <= 0 ? 0 : 1;
                    const c_x = vertices[indexC * 3] <= 0 ? 0 : 1;
                    const c_y = vertices[indexC * 3 + 1] <= 0 ? 0 : 1;
                    const c_z = vertices[indexC * 3 + 2] <= 0 ? 0 : 1;
                    const d_x = vertices[indexD * 3] <= 0 ? 0 : 1;
                    const d_y = vertices[indexD * 3 + 1] <= 0 ? 0 : 1;
                    const d_z = vertices[indexD * 3 + 2] <= 0 ? 0 : 1;

                    if (Math.abs(a_y - b_y) < Math.abs(a_x - b_x)) {
                        return [new Vector2(a_x, 1 - a_z), new Vector2(b_x, 1 - b_z), new Vector2(c_x, 1 - c_z), new Vector2(d_x, 1 - d_z)];
                    } else {
                        return [new Vector2(a_y, 1 - a_z), new Vector2(b_y, 1 - b_z), new Vector2(c_y, 1 - c_z), new Vector2(d_y, 1 - d_z)];
                    }
                },
            },
        });
        side_geometry.rotateX(-Math.PI / 2);
        const side_mesh = new Mesh(side_geometry, [null, side_meterial]);
        scene.add(side_mesh);
    });

    const canvas = renderer.domElement;
    canvas.addEventListener('mousemove', e => {
        const { offsetX: px, offsetY: py } = e;
        const x = 2 * px / canvas.width - 1;
        const y = -2 * py / canvas.height + 1;
        const ray = new Raycaster();
        ray.setFromCamera(new Vector2(x, y), camera);
        const objs = ray.intersectObjects(region_meshs);
        outlinePass.selectedObjects = objs.length ? [objs[0].object] : [];
    });
});
</script>

<template>
    <div ref="viewRef" class="w-screen h-screen overflow-hidden"></div>
</template>
